/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package pgentity;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import db.DBContext;
import db.PGKeys;
import db.RedisKey;
import db.mysql.DataRow;
import db.mysql.DataTable;
import db.mysql.DeleteExecutor;
import db.mysql.SQLBackgroundExecutor;
import db.mysql.SQLBatchStatement;
import db.mysql.SQLExecutor;
import db.mysql.SelectExecutor;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import pgentity.pool.PooledEntity;
import share.PGHelper;
import share.PGMacro;

/**
 *
 * @author Salm
 */
public class MailboxOld implements PooledEntity {
    private static final String MAIL_TABLE = "MAILS";
    private static final MailboxOld EMPTY_MAIL;
    private static final LoadingCache<String, MailboxOld> mailCache;
    static
    {
        mailCache = CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(100)
                .build(new MailboxLoader());
        
        EMPTY_MAIL = new MailboxOld("empty", Collections.EMPTY_LIST, 0);
    }
    
    private final String uid;
    private final RedisKey redisKey;
    private final List<MailboxOld.Mail> mails;
    private int unreadMail;
    
    private MailboxOld(String uid, List<MailboxOld.Mail> mails, int nUnread) {
        this.uid = uid;
        this.redisKey = redisKey(uid);
        this.mails = mails;
        this.unreadMail = nUnread;
    }
    
    public static MailboxOld getMails(String uid)
    {
        try
        {
            return mailCache.get(uid);
        } catch (Exception ex) {
            return EMPTY_MAIL;
        }
    }
    
    public static RedisKey redisKey(String uid)
    {
        return PGKeys.USERS.getChild(uid).getChild(PGKeys.FD_UNREAD_MAILS);
    }
    
    public static void destroy(String uid)
    {
        DBContext.Redis().del(redisKey(uid));
        DBContext.SQL().execute(DeleteExecutor.fromDesc(
                MAIL_TABLE,
                String.format("receiver = '%s'", uid)));
        
        mailCache.invalidate(uid);
    }
    
    public static void destroyLimit(final String uid, final int limit)
    {
        DBContext.SQL().execute(new SQLExecutor() {

            @Override
            public Object exec(Statement stm) throws SQLException {
                return stm.execute(String.format("CALL REMOVE_OLD_MAILS(%s, %d)", uid, limit));
            }
        });
        
        mailCache.invalidate(uid);
    }
    
    public static void sendMail(final String uid, final String sender,
            final String content, final long now)
    {
        DBContext.SQL().executeBackground(new SQLBackgroundExecutor() {
            @Override
            public String queryToken() {
                return "INSERT INTO MAILS (sender, receiver, time, content) VALUES(?, ?, ?, ?)";
            }

            @Override
            public int batchSizeLimit() {
                return 1000;
            }

            @Override
            public void prepare(SQLBatchStatement.Manager batch) {
                SQLBatchStatement stm = batch.createStatement();
                stm.setInt(1, PGHelper.toInt(sender));
                stm.setInt(2, PGHelper.toInt(uid));
                stm.setLong(3, now);
                stm.setString(4, content);
            }
        });
        
        DBContext.Redis().incr(redisKey(uid));
    }
    
    public Map<String, Object> buildAMF(int off, int len)
    {
        off = validOffset(off);
        len = validLen(off, len);
        Map<String, Object> data = new LinkedHashMap(len);

        for (int i = 0; i < len; ++i)
        {
            int index = off - i - 1;
            data.put(String.valueOf(index), mails.get(index).buildAMF());
        }

        return data;
    }
    
    public int getUnreadMail()
    {
        return this.unreadMail;
    }
    
    public void resetUnreadMail()
    {
        if (this.unreadMail > 0)
        {
            DBContext.Redis().decrBy(redisKey, unreadMail);
        }
        
        this.unreadMail = 0;
    }
    
    private int validOffset(int off)
    {
        if (off < 0)
        {
            return mails.size() + off + 1;
        }
        
        return off;
    }
    
    private int validLen(int off, int len)
    {
        return Math.min(len, off);
    }
    
    private static class MailboxLoader extends CacheLoader<String, MailboxOld>
    {
        @Override
        public MailboxOld load(String uid) throws Exception {
            DataTable dt = (DataTable) DBContext.SQL()
                    .execute(SelectExecutor.fromDesc(
                    MAIL_TABLE,
                    "`receiver` = " + uid + " ORDER BY `time` DESC LIMIT 0, 100",
                    "mail_id", "sender", "time", "content"));
            
            return mailFromTable(uid, dt);
        };

        private MailboxOld mailFromTable(String uid, DataTable dt) {
            List<Mail> mails = new ArrayList(dt.size());
            for (DataRow row : dt) {
                mails.add(Mail.fromDataRow(uid, row));
            }
            Collections.reverse(mails);
            
            int nUnread = PGHelper.toInt(DBContext.Redis().get(redisKey(uid)));
            
            return new MailboxOld(uid, mails, nUnread);
        }
    }
    
    private static class Mail
    {
        public static Mail fromDataRow(String uid, DataRow row)
        {
            Mail mail = new Mail();
            mail.mailID = row.get(PGMacro.MAIL_ID);
            mail.sendTime = PGHelper.toLong(row.get(PGMacro.MAIL_SEND_TIME));
            mail.sender = row.get(PGMacro.MAIL_SENDER);
            mail.content = row.get(PGMacro.MAIL_CONTENT);
            
            return mail;
        }
        
        private String mailID;

        private Long sendTime;
        private String sender;
        private String content;
        private Map<String, Object> amf = null;
        
        public Map<String, Object> buildAMF()
        {
            if (amf == null)
            {
                Map<String, Object> data = new HashMap(4);

                data.put(PGMacro.MAIL_ID, mailID);
                data.put(PGMacro.MAIL_SEND_TIME, sendTime);
                data.put(PGMacro.MAIL_SENDER, sender);
                data.put(PGMacro.MAIL_CONTENT, content);

                amf = data;
            }
            
            return amf;
        }
    }
}
